package com.suduku.calc;

import com.suduku.entity.Box;
import com.suduku.util.SudoUtil;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 数链-梯形（如果同一行中，数字n只有两个空白格B1，B2可以填写，并且空白格不在同一宫中。
 * 且存在另外一行，也只有两个空白格B3，B4可以填写数字n，也不在同一宫中。
 * 有两个空白格所在的列相同（B1与B3，或B2与B4）【因为都是从左往右查找】，
 * 另外两个点的宫在同一列中。则形成梯形数链。可以排除下图蓝色区域内的候选数字n。） <br/>
 *
 * 列方向同理。
 *
 * 测试数据：DataConstant.OTHER_SU_LIAN_T_01
 */
public class SuLianTrapezoidCalc extends AbstractCalc {

    /*
     * 1.遍历所有的候选数字n
     * 2.使用数字n遍历所有的行，查找某一行中，只有两个空白格的候选值中有数字n
     * 3.重新遍历所有行，并且当前行的下标大于第一行的下标。继续查找与第一次获得行相同的两个空白格
     * 4.校验是否形成梯形。
     * 4.1 B1和B3的Y坐标一致，并且B2和B4所在的GY坐标一致，而Y坐标不一致。
     * 4.1.1 如果符合要求，则可以排除B2所在宫中，Y坐标与B4相等的空白格候选数字n
     * 4.1.2                 排除B4所在宫中，Y坐标与B2相等的空白格候选数字n
     * 4.1 B2和B4的Y坐标一致，并且B1和B3所在的GY坐标一致，而Y坐标不一致。
     * 4.1.1 如果符合要求，则可以排除B1所在宫中，Y坐标与B3相等的空白格候选数字n
     * 4.1.2                 排除B3所在宫中，Y坐标与B1相等的空白格候选数字n
     *
     * 5.遍历列的逻辑与上述类似
     */
    @Override
    Box solve() {
        // 遍历候选数字
        for(Integer n : Box.INIT_LIST) {
            // 遍历所有的行
            Box clearBox = findAndClear(getXMap().entrySet(), n, Box::getY);
            if(clearBox != null) {
                return clearBox;
            }
            // 遍历所有列
            clearBox = findAndClear(getYMap().entrySet(), n, Box::getX);
            if(clearBox != null) {
                return clearBox;
            }
        }
        return null;
    }

    private Box findAndClear(Set<Map.Entry<Integer, List<Box>>> areaEntries, Integer n, Function<Box, Integer> getFun) {
        for (Map.Entry<Integer, List<Box>> l1Entry : areaEntries) {
            // 获取B1,B2两个空白格
            List<Box> b12List = SudoUtil.findBoxByCList(l1Entry.getValue(), n);
            if(SudoUtil.isTwoBox(b12List)) {
                // 查找B3,B4两个空白格
                for(Map.Entry<Integer, List<Box>> l2Entry : areaEntries) {
                    if(l2Entry.getKey() > l1Entry.getKey()) {
                        List<Box> b34List = SudoUtil.findBoxByCList(l2Entry.getValue(), n);
                        if(SudoUtil.isTwoBox(b34List)) {
                            // 确定了4个顶点，判断是否形成梯形
                            if(SudoUtil.isTrapezoid(b12List.get(0), b12List.get(1), b34List.get(0), b34List.get(1), getFun)) {
                                // 斜边在右边，排除b12List.get(1)和b34List.get(1) 所在宫中的空白格
                                Box box = clearBox(b12List.get(1), b34List.get(1), n, getFun);
                                if(box != null) {
                                    change(box, SudoUtil.getList(b12List, b34List), n);
                                    return box;
                                }
                            }
                            if(SudoUtil.isTrapezoid(b12List.get(1), b12List.get(0), b34List.get(1), b34List.get(0), getFun)) {
                                // 斜边在左边
                                Box box = clearBox(b12List.get(0), b34List.get(0), n, getFun);
                                if(box != null) {
                                    change(box, SudoUtil.getList(b12List, b34List), n);
                                    return box;
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 清理空白格 <br/>
     *
     * @param b2 梯形斜边的两个顶点
     * @param b4 梯形斜边的两个顶点
     * @param n 候选数字n
     * @param getFun 方法 代替 getY和 getX
     * @return "com.suduku.entity.Box"
     */
    private Box clearBox(Box b2, Box b4, Integer n, Function<Box, Integer> getFun) {
        List<Box> clearList = findClearList(b2, b4, n, getFun);
        for(Box box : clearList) {
            box.removeCList(n);
            return box;
        }
        return null;
    }

    /**
     * 功能描述: 查找可以清楚的空白格 <br/>
     *
     * @param b2 梯形斜边的两个顶点
     * @param b4 梯形斜边的两个顶点
     * @param n 候选数字n
     * @param getFun 方法 代替 getY和 getX
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    private List<Box> findClearList(Box b2, Box b4, Integer n, Function<Box, Integer> getFun) {
        // B2所在宫中，Y坐标等于B4的Y坐标
        List<Box> b2List = getGList(b2).stream()
                .filter(b -> b.isBlank() && getFun.apply(b).equals(getFun.apply(b4)) && b.getCList().contains(n))
                .collect(Collectors.toList());
        List<Box> b4List = getGList(b4).stream()
                .filter(b -> b.isBlank() && getFun.apply(b).equals(getFun.apply(b2)) && b.getCList().contains(n))
                .collect(Collectors.toList());
        b2List.addAll(b4List);
        return b2List;
    }

}
